/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Oct 7, 2006
* @author Fabio
*/
package org.python.pydev.navigator;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.navigator.IPipelinedTreeContentProvider;
import org.eclipse.ui.navigator.PipelinedShapeModification;
import org.eclipse.ui.navigator.PipelinedViewerUpdate;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.core.structure.TreeNode;
import org.python.pydev.navigator.elements.IWrappedResource;
import org.python.pydev.navigator.elements.ProjectConfigError;
import org.python.pydev.navigator.elements.PythonFile;
import org.python.pydev.navigator.elements.PythonFolder;
import org.python.pydev.navigator.elements.PythonProjectSourceFolder;
import org.python.pydev.navigator.elements.PythonResource;
import org.python.pydev.navigator.elements.PythonSourceFolder;
import org.python.pydev.plugin.nature.PythonNature;
/**
* This is the Model provider for python elements.
*
* It intercepts the adds/removes and changes the original elements for elements
* that actually reflect the python model (with source folder, etc).
*
*
* Tests for package explorer:
* 1. Start eclipse with a file deep in the structure and without having anything expanded in the tree, make a 'show in'
*
* @author Fabio
*/
public final class PythonModelProvider extends PythonBaseModelProvider implements IPipelinedTreeContentProvider {
/* (non-Javadoc)
* @see org.python.pydev.navigator.PythonBaseModelProvider#getChildren(java.lang.Object)
*/
@Override
public Object[] getChildren(Object parentElement) {
Object[] ret = super.getChildren(parentElement);
if (parentElement instanceof PythonProjectSourceFolder) {
PythonProjectSourceFolder projectSourceFolder = (PythonProjectSourceFolder) parentElement;
Set<Object> set = new HashSet<Object>();
fillChildrenForProject(set, (IProject) projectSourceFolder.getActualObject(), projectSourceFolder);
if (set.size() > 0) {
Object[] newRet = new Object[ret.length + set.size()];
System.arraycopy(ret, 0, newRet, 0, ret.length);
int i = ret.length;
for (Object o : set) {
newRet[i] = o;
i++;
}
ret = newRet;
}
}
return ret;
}
/**
* This method basically replaces all the elements for other resource elements
* or for wrapped elements.
*
* @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#getPipelinedChildren(java.lang.Object, java.util.Set)
*/
@SuppressWarnings("unchecked")
public void getPipelinedChildren(Object parent, Set currentElements) {
if (DEBUG) {
System.out.println("getPipelinedChildren for: " + parent);
}
if (parent instanceof IWrappedResource) {
//Note: It seems that this NEVER happens (IWrappedResources only have getChildren called, not getPipelinedChildren)
Object[] children = getChildren(parent);
currentElements.clear();
currentElements.addAll(Arrays.asList(children));
if (DEBUG) {
System.out.println("getPipelinedChildren RETURN: " + currentElements);
}
if (parent instanceof PythonProjectSourceFolder) {
PythonProjectSourceFolder projectSourceFolder = (PythonProjectSourceFolder) parent;
IProject project = (IProject) projectSourceFolder.getActualObject();
fillChildrenForProject(currentElements, project, parent);
}
return;
} else if (parent instanceof IWorkspaceRoot) {
switch (topLevelChoice.getRootMode()) {
case TopLevelProjectsOrWorkingSetChoice.WORKING_SETS:
currentElements.clear();
currentElements.addAll(getWorkingSetsCallback.call((IWorkspaceRoot) parent));
case TopLevelProjectsOrWorkingSetChoice.PROJECTS:
//Just go on...
}
} else if (parent instanceof IWorkingSet) {
IWorkingSet workingSet = (IWorkingSet) parent;
currentElements.clear();
currentElements.addAll(Arrays.asList(workingSet.getElements()));
} else if (parent instanceof TreeNode) {
TreeNode treeNode = (TreeNode) parent;
currentElements.addAll(treeNode.getChildren());
} else if (parent instanceof IProject) {
IProject project = (IProject) parent;
fillChildrenForProject(currentElements, project, getResourceInPythonModel(project));
}
PipelinedShapeModification modification = new PipelinedShapeModification(parent, currentElements);
convertToPythonElementsAddOrRemove(modification, true);
if (DEBUG) {
System.out.println("getPipelinedChildren RETURN: " + modification.getChildren());
}
}
@SuppressWarnings("unchecked")
private void fillChildrenForProject(Set currentElements, IProject project, Object parent) {
ProjectInfoForPackageExplorer projectInfo = getProjectInfo(project);
if (projectInfo != null) {
currentElements.addAll(projectInfo.configErrors);
InterpreterInfoTreeNode<LabelAndImage> projectInfoTreeStructure = projectInfo.getProjectInfoTreeStructure(
project, parent);
if (projectInfoTreeStructure != null) {
currentElements.add(projectInfoTreeStructure);
}
}
}
/**
* This method basically replaces all the elements for other resource elements
* or for wrapped elements.
*
* @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#getPipelinedElements(java.lang.Object, java.util.Set)
*/
public void getPipelinedElements(Object input, Set currentElements) {
if (DEBUG) {
System.out.println("getPipelinedElements for: " + input);
}
getPipelinedChildren(input, currentElements);
}
/**
* This method basically get the actual parent for the resource or the parent
* for a wrapped element (which may be a resource or a wrapped resource).
*
* @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#getPipelinedParent(java.lang.Object, java.lang.Object)
*/
public Object getPipelinedParent(Object object, Object aSuggestedParent) {
if (DEBUG) {
System.out.println("getPipelinedParent for: " + object);
}
//Now, we got the parent for the resources correctly at this point, but there's one last thing we may need to
//do: the actual parent may be a working set!
Object p = this.topLevelChoice.getWorkingSetParentIfAvailable(object, getWorkingSetsCallback);
if (p != null) {
aSuggestedParent = p;
} else if (object instanceof IWrappedResource) {
IWrappedResource resource = (IWrappedResource) object;
Object parentElement = resource.getParentElement();
if (parentElement != null) {
aSuggestedParent = parentElement;
}
} else if (object instanceof TreeNode<?>) {
TreeNode<?> treeNode = (TreeNode<?>) object;
return treeNode.getParent();
} else if (object instanceof ProjectConfigError) {
ProjectConfigError configError = (ProjectConfigError) object;
return configError.getParent();
}
if (DEBUG) {
System.out.println("getPipelinedParent RETURN: " + aSuggestedParent);
}
return aSuggestedParent;
}
/**
* This method intercepts some addition to the tree and converts its elements to python
* elements.
*
* @see org.eclipse.ui.navigator.IPipelinedTreeContentProvider#interceptAdd(org.eclipse.ui.navigator.PipelinedShapeModification)
*/
public PipelinedShapeModification interceptAdd(PipelinedShapeModification addModification) {
if (DEBUG) {
System.out.println("interceptAdd");
}
convertToPythonElementsAddOrRemove(addModification, true);
return addModification;
}
public boolean interceptRefresh(PipelinedViewerUpdate refreshSynchronization) {
if (DEBUG) {
System.out.println("interceptRefresh");
}
return convertToPythonElementsUpdateOrRefresh(refreshSynchronization.getRefreshTargets());
}
public PipelinedShapeModification interceptRemove(PipelinedShapeModification removeModification) {
if (DEBUG) {
System.out.println("interceptRemove");
}
convertToPythonElementsAddOrRemove(removeModification, false);
return removeModification;
}
public boolean interceptUpdate(PipelinedViewerUpdate updateSynchronization) {
if (DEBUG) {
debug("Before interceptUpdate", updateSynchronization);
}
boolean ret = convertToPythonElementsUpdateOrRefresh(updateSynchronization.getRefreshTargets());
if (DEBUG) {
debug("After interceptUpdate", updateSynchronization);
}
return ret;
}
/**
* Helper for debugging the things we have in an update
*/
private void debug(String desc, PipelinedViewerUpdate updateSynchronization) {
System.out.println("\nDesc:" + desc);
System.out.println("Refresh targets:");
for (Object o : updateSynchronization.getRefreshTargets()) {
System.out.println(o);
}
}
/**
* Helper for debugging the things we have in a modification
*/
private void debug(String desc, PipelinedShapeModification modification) {
System.out.println("\nDesc:" + desc);
Object parent = modification.getParent();
System.out.println("Parent:" + parent);
System.out.println("Children:");
for (Object o : modification.getChildren()) {
System.out.println(o);
}
}
/**
* This is the function that is responsible for restoring the paths in the tree.
*/
public void restoreState(IMemento memento) {
new PyPackageStateSaver(this, viewer, memento).restoreState();
}
/**
* This is the function that is responsible for saving the paths in the tree.
*/
public void saveState(IMemento memento) {
new PyPackageStateSaver(this, viewer, memento).saveState();
}
/**
* Converts the shape modification to use Python elements.
*
* @param modification: the shape modification to convert
* @param isAdd: boolean indicating whether this convertion is happening in an add operation
*/
private void convertToPythonElementsAddOrRemove(PipelinedShapeModification modification, boolean isAdd) {
if (DEBUG) {
debug("Before", modification);
}
Object parent = modification.getParent();
if (parent instanceof IContainer) {
IContainer parentContainer = (IContainer) parent;
Object pythonParent = getResourceInPythonModel(parentContainer, true);
if (pythonParent instanceof IWrappedResource) {
IWrappedResource parentResource = (IWrappedResource) pythonParent;
modification.setParent(parentResource);
wrapChildren(parentResource, parentResource.getSourceFolder(), modification.getChildren(), isAdd);
} else if (pythonParent == null) {
Object parentInWrap = parentContainer;
PythonSourceFolder sourceFolderInWrap = null;
//this may happen when a source folder is added or some element that still doesn't have it's parent in the model...
//so, we have to get the parent's parent until we actually 'know' that it is not in the model (or until we run
//out of parents to try)
//the case in which we reproduce this is Test 1 (described in the class)
FastStack<Object> found = new FastStack<Object>(20);
while (true) {
//add the current to the found
if (parentContainer == null) {
break;
}
found.push(parentContainer);
if (parentContainer instanceof IProject) {
//we got to the project without finding any part of a python model already there, so, let's see
//if any of the parts was actually a source folder (that was still not added)
tryCreateModelFromProject((IProject) parentContainer, found);
//and now, if it was created, try to convert it to the python model (without any further add)
convertToPythonElementsUpdateOrRefresh(modification.getChildren());
return;
}
Object p = getResourceInPythonModel(parentContainer, true);
if (p instanceof IWrappedResource) {
IWrappedResource wrappedResource = (IWrappedResource) p;
sourceFolderInWrap = wrappedResource.getSourceFolder();
while (found.size() > 0) {
Object f = found.pop();
if (f instanceof IResource) {
//no need to create it if it's already in the model!
Object child = sourceFolderInWrap.getChild((IResource) f);
if (child != null && child instanceof IWrappedResource) {
wrappedResource = (IWrappedResource) child;
continue;
}
}
//creating is enough to add it to the model
if (f instanceof IFile) {
wrappedResource = new PythonFile(wrappedResource, (IFile) f, sourceFolderInWrap);
} else if (f instanceof IFolder) {
wrappedResource = new PythonFolder(wrappedResource, (IFolder) f, sourceFolderInWrap);
}
}
parentInWrap = wrappedResource;
break;
}
parentContainer = parentContainer.getParent();
}
wrapChildren(parentInWrap, sourceFolderInWrap, modification.getChildren(), isAdd);
}
} else if (parent == null) {
wrapChildren(null, null, modification.getChildren(), isAdd);
}
if (DEBUG) {
debug("After", modification);
}
}
/**
* Given a Path from the 1st child of the project, will try to create that path in the python model.
* @param project the project
* @param found a stack so that the last element added is the leaf of the path we want to discover
*/
private void tryCreateModelFromProject(IProject project, FastStack<Object> found) {
PythonNature nature = PythonNature.getPythonNature(project);
if (nature == null) {
return;//if the python nature is not available, we won't have any python elements here
}
Set<String> sourcePathSet = new HashSet<String>();
try {
sourcePathSet = nature.getPythonPathNature().getProjectSourcePathSet(true);
} catch (CoreException e) {
Log.log(e);
}
Object currentParent = project;
PythonSourceFolder pythonSourceFolder = null;
for (Iterator<Object> it = found.topDownIterator(); it.hasNext();) {
Object child = it.next();
if (child instanceof IFolder || child instanceof IProject) {
if (pythonSourceFolder == null) {
pythonSourceFolder = tryWrapSourceFolder(currentParent, (IContainer) child, sourcePathSet);
if (pythonSourceFolder != null) {
currentParent = pythonSourceFolder;
} else if (child instanceof IContainer) {
currentParent = (IContainer) child;
}
//just go on (if we found the source folder or not, because if we found, that's ok, and if
//we didn't, then the children will not be in the python model anyway)
continue;
}
}
if (pythonSourceFolder != null) {
IWrappedResource r = doWrap(currentParent, pythonSourceFolder, child);
if (r != null) {
child = r;
}
}
currentParent = child;
}
}
/**
* Actually wraps some resource into a wrapped resource.
*
* @param parent this is the parent
* it may be null -- in the case of a remove
* it may be a wrapped resource (if it is in the python model)
* it may be a resource (if it is a source folder)
*
*
* @param pythonSourceFolder this is the python source folder for the resource (it may be null if the resource itself is a source folder
* or if it is actually a resource that has already been removed)
* @param currentChildren those are the children that should be wrapped
* @param isAdd whether this is an add operation or not
* @return
*/
@SuppressWarnings("unchecked")
protected boolean wrapChildren(Object parent, PythonSourceFolder pythonSourceFolder, Set currentChildren,
boolean isAdd) {
LinkedHashSet convertedChildren = new LinkedHashSet();
for (Iterator childrenItr = currentChildren.iterator(); childrenItr.hasNext();) {
Object child = childrenItr.next();
if (child == null) {
//only case when a child is removed and another one is not added (null)
childrenItr.remove();
continue;
}
//yeap, it may be an object that's not an actual resource (created by some other plugin... just continue)
if (!(child instanceof IResource)) {
continue;
}
Object existing = getResourceInPythonModel((IResource) child, true);
if (existing == null) {
if (isAdd) {
//add
IWrappedResource w = doWrap(parent, pythonSourceFolder, child);
if (w != null) { //if it is null, it is not below a python source folder
childrenItr.remove();
convertedChildren.add(w);
}
} else {
continue; //it has already been removed
}
} else { //existing != null
childrenItr.remove();
convertedChildren.add(existing);
if (!isAdd) {
//also remove it from the model
IWrappedResource wrapped = (IWrappedResource) existing;
wrapped.getSourceFolder().removeChild((IResource) child);
}
}
}
//if we did have some wrapping... go on and add them to the out list (and return true)
if (!convertedChildren.isEmpty()) {
currentChildren.addAll(convertedChildren);
return true;
}
//nothing happened, so, just say it
return false;
}
/**
* This method tries to wrap a given resource as a wrapped resource (if possible)
*
* @param parent the parent of the wrapped resource
* @param pythonSourceFolder the source folder that contains this resource
* @param child the object that should be wrapped
* @return the object as an object from the python model
*/
protected IWrappedResource doWrap(Object parent, PythonSourceFolder pythonSourceFolder, Object child) {
if (child instanceof IProject) {
//ok, let's see if the child is a source folder (as the default project can be the actual source folder)
if (pythonSourceFolder == null && parent != null) {
PythonSourceFolder f = doWrapPossibleSourceFolder(parent, (IProject) child);
if (f != null) {
return f;
}
}
} else if (child instanceof IFolder) {
IFolder folder = (IFolder) child;
//it may be a PythonSourceFolder
if (pythonSourceFolder == null && parent != null) {
PythonSourceFolder f = doWrapPossibleSourceFolder(parent, folder);
if (f != null) {
return f;
}
}
if (pythonSourceFolder != null) {
return new PythonFolder((IWrappedResource) parent, folder, pythonSourceFolder);
}
} else if (child instanceof IFile) {
if (pythonSourceFolder != null) {
//if the python source folder is null, that means that this is a file that is not actually below a source folder -- so, don't wrap it
return new PythonFile((IWrappedResource) parent, (IFile) child, pythonSourceFolder);
}
} else if (child instanceof IResource) {
if (pythonSourceFolder != null) {
return new PythonResource((IWrappedResource) parent, (IResource) child, pythonSourceFolder);
}
} else {
throw new RuntimeException("Unexpected class:" + child.getClass());
}
return null;
}
/**
* Try to wrap a folder or project as a source folder...
*/
private PythonSourceFolder doWrapPossibleSourceFolder(Object parent, IContainer container) {
try {
IProject project;
if (!(container instanceof IProject)) {
project = ((IContainer) parent).getProject();
} else {
project = (IProject) container;
}
PythonNature nature = PythonNature.getPythonNature(project);
if (nature != null) {
//check for source folder
Set<String> sourcePathSet = nature.getPythonPathNature().getProjectSourcePathSet(true);
PythonSourceFolder newSourceFolder = tryWrapSourceFolder(parent, container, sourcePathSet);
if (newSourceFolder != null) {
return newSourceFolder;
}
}
} catch (CoreException e) {
throw new RuntimeException(e);
}
return null;
}
/**
* This method checks if the given folder can be wrapped as a source-folder, and if that's possible, creates and returns
* it
* @return a created source folder or null if it couldn't be created.
*/
private PythonSourceFolder tryWrapSourceFolder(Object parent, IContainer container, Set<String> sourcePathSet) {
IPath fullPath = container.getFullPath();
if (sourcePathSet.contains(fullPath.toString())) {
PythonSourceFolder sourceFolder;
if (container instanceof IFolder) {
sourceFolder = new PythonSourceFolder(parent, (IFolder) container);
} else if (container instanceof IProject) {
sourceFolder = new PythonProjectSourceFolder(parent, (IProject) container);
} else {
return null; //some other container we don't know how to treat!
}
//System.out.println("Created source folder: "+ret[i]+" - "+folder.getProject()+" - "+folder.getProjectRelativePath());
Set<PythonSourceFolder> sourceFolders = getProjectSourceFolders(container.getProject());
sourceFolders.add((PythonSourceFolder) sourceFolder);
return sourceFolder;
}
return null;
}
/**
* Converts elements to the python model -- but only creates it if it's parent is found in the python model
*/
@SuppressWarnings("unchecked")
protected boolean convertToPythonElementsUpdateOrRefresh(Set currentChildren) {
LinkedHashSet convertedChildren = new LinkedHashSet();
for (Iterator childrenItr = currentChildren.iterator(); childrenItr.hasNext();) {
Object child = childrenItr.next();
if (child == null) {
//only case when a child is removed and another one is not added (null)
childrenItr.remove();
continue;
}
if (child instanceof IResource && !(child instanceof IWrappedResource)) {
IResource res = (IResource) child;
Object resourceInPythonModel = getResourceInPythonModel(res, true);
if (resourceInPythonModel != null) {
//if it is in the python model, just go on
childrenItr.remove();
convertedChildren.add(resourceInPythonModel);
} else {
//now, if it's not but its parent is, go on and create it
IContainer p = res.getParent();
if (p == null) {
continue;
}
Object pythonParent = getResourceInPythonModel(p, true);
if (pythonParent instanceof IWrappedResource) {
IWrappedResource parent = (IWrappedResource) pythonParent;
if (res instanceof IProject) {
throw new RuntimeException("A project's parent should never be an IWrappedResource!");
} else if (res instanceof IFolder) {
childrenItr.remove();
convertedChildren.add(new PythonFolder(parent, (IFolder) res, parent.getSourceFolder()));
} else if (res instanceof IFile) {
childrenItr.remove();
convertedChildren.add(new PythonFile(parent, (IFile) res, parent.getSourceFolder()));
} else if (child instanceof IResource) {
childrenItr.remove();
convertedChildren.add(new PythonResource(parent, (IResource) child, parent
.getSourceFolder()));
}
} else if (res instanceof IFolder) {
//ok, still not in the model... could it be a PythonSourceFolder
IFolder folder = (IFolder) res;
IProject project = folder.getProject();
if (project == null) {
continue;
}
PythonNature nature = PythonNature.getPythonNature(project);
if (nature == null) {
continue;
}
Set<String> sourcePathSet = new HashSet<String>();
try {
sourcePathSet = nature.getPythonPathNature().getProjectSourcePathSet(true);
} catch (CoreException e) {
Log.log(e);
}
PythonSourceFolder wrapped = tryWrapSourceFolder(p, folder, sourcePathSet);
if (wrapped != null) {
childrenItr.remove();
convertedChildren.add(wrapped);
}
}
}
}
}
if (!convertedChildren.isEmpty()) {
currentChildren.addAll(convertedChildren);
return true;
}
return false;
}
}